module AboutYou
  module SDK
    ###
    # The Factory namespace
    ###
    module Factory
      ###
      # This class creates model objects from a given api response
      # the methods will get called automatically from the query so no need
      # to call them by hand
      #
      # author:: Collins GmbH & Co KG
      ###
      class DefaultModelFactory
        # The client which performs the api calls
        attr_accessor :shop_api
        # the category manager responsible for managing the categories
        attr_accessor :category_manager
        # the facet manager responsible for managing the facets
        attr_accessor :facet_manager

        ###
        # Constructor for AboutYou::SDK::Factory::DefaultModelFactory
        #
        # * *Args*    :
        #   - +shop_api+ -> the client which should perform the api calls
        #   - +category_manager+ -> the category manager responsible for managing the categories
        #   - +facet_manager+ -> the facet manager responsible for managing the facets
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Factory::DefaultModelFactory
        ###
        def initialize(shop_api = nil)
          self.shop_api = shop_api
        end

        ###
        # this method initializes the managers
        #
        # * *Args*    :
        #   - +facet_manager+ -> the facet manager responsible for managing the facets
        #   - +category_manager+ -> the category manager responsible for managing the categories
        ###
        def initialize_managers(facet_manager, category_manager)
          self.category_manager = category_manager
          self.facet_manager = facet_manager
          AboutYou::SDK::Model::FacetGroupSet.facet_manager = self.facet_manager
        end

        ###
        # sets the baseimage url for the image model
        #
        # * *Args*    :
        #   - +base_url+ -> the url which should be used by the image model
        ###
        def base_image_url=(base_url)
          AboutYou::SDK::Model::Image.base_url = base_url
        end

        ###
        # creates an autocomplete model
        #
        # * *Args*    :
        #   - +json_object+ -> the api response in json format
        #   - +query+ -> the query sent to the api
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Model::Autocomplete
        ###
        def create_autocomplete(json_object, _query)
          AboutYou::SDK::Model::Autocomplete.create_from_json(json_object, self)
        end

        ###
        # creates a basket model
        #
        # * *Args*    :
        #   - +json_object+ -> the api response in json format
        #   - +query+ -> the query sent to the api
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Model::Basket
        ###
        def create_basket(json_object, _query)
          AboutYou::SDK::Model::Basket.create_from_json(json_object, self)
        end

        ###
        # creates a basket item model
        #
        # * *Args*    :
        #   - +json_object+ -> the api response in json format
        #   - +products+ -> the product models for which items should be created
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Model::BasketItem
        ###
        def create_basket_item(json_object, products)
          AboutYou::SDK::Model::Basket::BasketItem.create_from_json(
            json_object,
            products
          )
        end

        ###
        # creates a basket set model
        #
        # * *Args*    :
        #   - +json_object+ -> the api response in json format
        #   - +products+ -> the product models for which items should be created
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Model::BasketSet
        ###
        def create_basket_set(json_object, products)
          AboutYou::SDK::Model::BasketSet.create_from_json(
            json_object,
            self,
            products
          )
        end

        ###
        # creates a basket setitem model
        #
        # * *Args*    :
        #   - +json_object+ -> the api response in json format
        #   - +products+ -> the product models for which items should be created
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Model::BasketSetItem
        ###
        def create_basket_set_item(json_object, products)
          AboutYou::SDK::Model::BasketSetItem.create_from_json(
            json_object,
            products
          )
        end

        ###
        # creates a category model
        #
        # * *Args*    :
        #   - +json_object+ -> the api response in json format
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Model::Category
        ###
        def create_category(json_object)
          AboutYou::SDK::Model::Category.create_from_json(
            json_object,
            category_manager
          )
        end

        ###
        # creates a category tree model
        #
        # * *Args*    :
        #   - +jsonArray+ -> an Array containing the api response
        #   - +_query+ -> the _query sent to the api
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Model::CategoryTree
        ###
        def create_category_tree(json_array, _query)
          initialize_category_manager(json_array)
          AboutYou::SDK::Model::CategoryTree.new(category_manager)
        end

        ###
        # creates a facet model
        #
        # * *Args*    :
        #   - +json_object+ -> the api response in json format
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Model::Facet
        ###
        def create_facet(json_object)
          AboutYou::SDK::Model::Facet.create_from_json(json_object)
        end

        ###
        # creates a Hash containing one or multiple pairs of
        # facetKey => AboutYou::SDK::Model::Facet
        #
        # * *Args*    :
        #   - +jsonArray+ -> an Array containing the api response
        #
        # * *Returns* :
        #   - a Hash containing pairs of facetKey => AboutYou::SDK::Model::Facet
        ###
        def create_facet_list(json_array, _query)
          facets = {}
          json_array.each do |json_facet|
            facet = create_facet(json_facet)
            key = facet.unique_key
            facets[key] = facet
          end

          facets
        end

        ###
        # creates a Hash containing one or multiple pairs of
        # facetKey => AboutYou::SDK::Model::Facet
        #
        # * *Args*    :
        #   - +json_object+ -> the api response in json format
        #   - +query+ -> the query sent to the api
        #
        # * *Returns* :
        #   - a Hash containing pairs of facetKey => AboutYou::SDK::Model::Facet
        ###
        def create_facets_list(json_object, query = nil)
          create_facet_list(json_object['facet'], query)
        end

        ###
        # creates a Hash containing one or multiple pairs of
        # group_id => instance of AboutYou::SDK::Model::FacetCounts
        #
        # * *Args*    :
        #   - +json_object+ -> the api response in json format
        #
        # * *Returns* :
        #   - a Hash containing pairs of group_id => instance of AboutYou::SDK::Model::FacetCounts
        ###
        def create_facets_counts(json_object)
          facets_counts = {}

          json_object['facets'].each do |group_id, json_result_facet|
            next unless group_id[/\d/]

            facet_counts = term_facets(
              group_id,
              json_result_facet['terms']
            )
            facets_counts[group_id] =
              AboutYou::SDK::Model::FacetCounts.create_from_json(
                group_id,
                json_result_facet,
                facet_counts
              )
          end

          facets_counts
        end

        ###
        # creates an Array containing one or multiple instances of
        # AboutYou::SDK::Model::FacetCount
        #
        # * *Args*    :
        #   - +group_id+ -> the group id of the facets
        #   - +jsonTerms+ -> the single facet terms in json format
        #
        # * *Returns* :
        #   - an Array containing instances of AboutYou::SDK::Model::FacetCount
        ###
        def term_facets(group_id, json_terms)
          facet_counts = []
          json_terms.each do |json_term|
            id = Integer(json_term['term'])
            facet = facet_manager.facet(group_id, id)
            next unless facet

            # TODO: Handle error, write test
            facet_counts.push(AboutYou::SDK::Model::FacetCount.new(
                facet,
                json_term['count']
              )
            )
          end

          facet_counts
        end

        ###
        # creates an Array containing all facet types available
        #
        # * *Args*    :
        #   - +jsonArray+ -> an Array containing the api response
        #   - +_query+ -> the _query sent to the api
        #
        # * *Returns* :
        #   - an Array containing all facet types available
        ###
        def create_facet_types(json_array, _query)
          json_array
        end

        ###
        # creates an image model
        #
        # * *Args*    :
        #   - +json_object+ -> the api response in json format
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Model::Image
        ###
        def create_image(json_object)
          AboutYou::SDK::Model::Image.create_from_json(json_object)
        end

        ###
        # creates a product model
        #
        # * *Args*    :
        #   - +json_object+ -> the api response in json format
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Model::Product
        ###
        def create_product(json_object)
          AboutYou::SDK::Model::Product.create_from_json(
            json_object,
            self,
            shop_api.app_id
          )
        end

        ###
        # creates a variants result model
        #
        # * *Args*    :
        #   - +json_object+ -> the api response in json format
        #   - +_query+ -> the _query sent to the api
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Model::VariantsResult
        ###
        def create_variants_result(json_object, _query)
          variants = {}
          errors = []
          product_ids = []
          product_search_result = false

          json_object.each do |id, data|
            if data['error_code']
              errors.push(id)
            else
              variants[data['id']] = data['product_id']
              product_ids.push(data['product_id'])
            end
          end

          if product_ids.count > 0
            product_ids = product_ids.uniq
            # search products for valid variants

            product_search_result = shop_api.fetch_products_by_ids(
                product_ids, [
                  AboutYou::SDK::Criteria::ProductFields::ATTRIBUTES_MERGED,
                  AboutYou::SDK::Criteria::ProductFields::BRAND,
                  AboutYou::SDK::Criteria::ProductFields::CATEGORIES,
                  AboutYou::SDK::Criteria::ProductFields::DEFAULT_IMAGE,
                  AboutYou::SDK::Criteria::ProductFields::DEFAULT_VARIANT,
                  AboutYou::SDK::Criteria::ProductFields::DESCRIPTION_LONG,
                  AboutYou::SDK::Criteria::ProductFields::DESCRIPTION_SHORT,
                  AboutYou::SDK::Criteria::ProductFields::IS_ACTIVE,
                  AboutYou::SDK::Criteria::ProductFields::IS_SALE,
                  AboutYou::SDK::Criteria::ProductFields::MAX_PRICE,
                  AboutYou::SDK::Criteria::ProductFields::MIN_PRICE,
                  AboutYou::SDK::Criteria::ProductFields::VARIANTS
                ]
              )
          end

          AboutYou::SDK::Model::VariantsResult.create(
            variants,
            errors,
            product_search_result
          )
        end

        ###
        # creates a single product model
        #
        # * *Args*    :
        #   - +json_object+ -> the api response in json format
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Model::Product
        ###
        def create_single_product(json_object)
          createProduct(json_object)
        end

        ###
        # creates a products result model
        #
        # * *Args*    :
        #   - +json_object+ -> the api response in json format
        #   - +_query+ -> the _query sent to the api
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Model::ProductsResult
        ###
        def create_products_result(json_object, _query)
          AboutYou::SDK::Model::ProductsResult.create_from_json(
            json_object,
            self
          )
        end

        ###
        # creates a products eanresult model
        #
        # * *Args*    :
        #   - +json_object+ -> the api response in json format
        #   - +_query+ -> the _query sent to the api
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Model::ProductsEanResult
        ###
        def create_products_ean_result(json_object, _query)
          AboutYou::SDK::Model::ProductsEansResult.create_from_json(
            json_object,
            self
          )
        end

        ###
        # creates a product searchresult model
        #
        # * *Args*    :
        #   - +json_object+ -> the api response in json format
        #   - +_query+ -> the _query sent to the api
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Model::ProductSearchResult
        ###
        def create_product_search_result(json_object, _query)
          AboutYou::SDK::Model::ProductSearchResult.create_from_json(
            json_object,
            self
          )
        end

        ###
        # creates a categories result model
        #
        # * *Args*    :
        #   - +json_object+ -> the api response in json format
        #   - +query+ -> the query sent to the api
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Model::CategoriesResult
        ###
        def create_categories_result(json_object, query)
          AboutYou::SDK::Model::CategoriesResult.create_from_json(
            json_object,
            query['ids'],
            self
          )
        end

        ###
        # creates an Array containing suggests
        #
        # * *Args*    :
        #   - +jsonArray+ -> an Array with the api response
        #   - +_query+ -> the _query sent to the api
        #
        # * *Returns* :
        #   - an Array containing suggests
        ###
        def create_suggest(jsonArray, _query)
          jsonArray
        end

        ###
        # creates a variant model
        #
        # * *Args*    :
        #   - +json_object+ -> the api response in json format
        #   - +product+ -> the product of the variant which should be created
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Model::Variant
        ###
        def create_variant(json_object, product)
          AboutYou::SDK::Model::Variant.create_from_json(
            json_object,
            self,
            product
          )
        end

        ###
        # creates an order model
        #
        # * *Args*    :
        #   - +json_object+ -> the api response in json format
        #   - +_query+ -> the _query sent to the api
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Model::Order
        ###
        def create_order(json_object, _query)
          basket = create_basket(json_object['basket'])

          AboutYou::SDK::Model::Order.new(json_object['order_id'], basket)
        end

        ###
        # creates an initiate order model
        #
        # * *Args*    :
        #   - +json_object+ -> the api response in json format
        #   - +_query+ -> the _query sent to the api
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Model::InitiateOrder
        ###
        def initiate_order(json_object, _query)
          AboutYou::SDK::Model::InitiateOrder.create_from_json(json_object)
        end

        ###
        # creates a Has containing one or multiple pairs of
        # app_id => instance of AboutYou::SDK::Model::App
        #
        # * *Args*    :
        #   - +json_object+ -> the api response in json format
        #   - +_query+ -> the _query sent to the api
        #
        # * *Returns* :
        #   - a Hash containing pairs of app_id => instance of AboutYou::SDK::Model::App
        ###
        def create_child_apps(json_object, _query)
          apps = {}
          json_object['child_apps'].each do |json_app|
            app = create_app(json_app)
            apps[app.id] = app
          end

          apps
        end

        ###
        # creates an app model
        #
        # * *Args*    :
        #   - +json_object+ -> the api response in json format
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Model::App
        ###
        def create_app(json_object)
          AboutYou::SDK::Model::App.create_from_json(json_object)
        end

        ###
        # creates an spell correction result
        #
        # * *Args*    :
        #   - +json_array+ -> the api response in an array
        #
        # * *Returns* :
        #   - an Array containing String
        ###
        def create_spell_correction(json_array, _query)
          json_array
        end

        ###
        # this methods initiates the category manager
        #
        # * *Args*    :
        #   - +json_object+ -> the api response in json format
        ###
        def initialize_category_manager(json_object)
          category_manager.parse_json(json_object, self)
        end

        ###
        # this methods updates the facet manager
        #
        # * *Args*    :
        #   - +json_object+ -> the api response in json format
        #   - +query+ -> the query sent to the api
        ###
        def update_facet_manager(json_object, query)
          facet_manager.parse_json(json_object, self, query)
        end

        ###
        # this method creates a price range model
        #
        # * *Args*    :
        #   - +json_object+ -> the api response in json format
        #
        # * *Returns* :
        #   - an Array containing instances of AboutYou::SDK::Model::PriceRange
        ###
        def create_price_ranges(json_object)
          price_ranges = []
          json_object['ranges'].each do |range|
            price_ranges.push(
              AboutYou::SDK::Model::PriceRange.create_from_json(
                range
              )
            )
          end

          price_ranges
        end

        ###
        # this method creates a sale counts model
        #
        # * *Args*    :
        #   - +json_object+ -> the api response in json format
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Model::SaleCounts
        ###
        def create_sale_facet(json_object)
          AboutYou::SDK::Model::SaleCounts.create_from_json(json_object)
        end

        ###
        # this method creates the facets for given categories
        #
        # * *Args*    :
        #   - +jsonArray+ -> an Array containing the Api response
        #
        # * *Returns* :
        #   - a Hash containing pairs of category_id => category
        ###
        def create_categories_facets(json_array)
          category_manager = self.category_manager

          flatten_categories = {}
          json_array.each do |item|
            id = item['term']
            category = category_manager.category(id)
            next unless category

            category.product_count = item['count']
            flatten_categories[id] = category
          end

          flatten_categories
        end
        
        ###
        # this method creates the brand model
        #
        # * *Args*    :
        #   - +json_object+ -> the response from the api in json format
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Model::Brand
        ###
        def create_brand(json_object)
          AboutYou::SDK::Model::Brand.create_from_json(json_object)
        end

        ###
        # this method tries to handle errors which are received from the api
        #
        # * *Args*    :
        #   - +json+ -> the api response in json format
        #   - +resultKey+ -> the result key received from the api
        #   - +isMultiRequest+ -> determines whether the api-request was multiquery or not
        #
        # * *Fails* :
        #   - if the result_key is not basket and the json response does not contain order_lines
        ###
        def pre_handle_error(json, result_key, is_multi_request)
          return if result_key == 'basket' && json['order_lines']

          fail 'ResultError!' + json + is_multi_request
        end
      end
    end
  end
end
